package ui;

import java.awt.MenuBar;
import java.io.IOException;
import java.io.InputStream;
import java.nio.FloatBuffer;
import java.text.DecimalFormat;
import java.util.List;
import java.util.Map;

import javax.media.opengl.GL2;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLProfile;
import javax.media.opengl.fixedfunc.GLMatrixFunc;
import javax.media.opengl.glu.GLU;

import service.PlanetService;
import service.PlanetServiceImpl;
import templates.JOGLTemplate;
import util.Parameters;
import util.SuperFormulaParameters;
import util.Util3D;

import com.google.common.collect.Maps;
import com.jogamp.opengl.util.texture.Texture;
import com.jogamp.opengl.util.texture.TextureData;
import com.jogamp.opengl.util.texture.TextureIO;

import core.NewOption;
import core.Planet;

/**
 * @author sebastien
 * @since 0.0.1-SNAPSHOT
 */
public class Project extends JOGLTemplate {

	private static final int CURRENT_DISTANCE_RAPPORT = 1000;
	private static final int LIGHT_SPEED = 300;

	// Constants
	private static final int WINDOW_WIDTH = 800;
	private static final int WINDOW_HEIGHT = 600;
	private static final String WINDOW_TITLE = "Solar System";

	// Fields
	/** Dernier update du temps de rafraichissement */
	private long lastTimeFPSRendering;
	/** Le texte affichant le FPS à l'écran */
	private Object fpsText;
	/** Le fps calculé suivant le temps de rendu */
	private int fps;
	/** Options d'affichage des éléments du projet (remplace les booléens dans le projet) */
	private final ProjectOptions projectOptions;
	/** Distance à l'origine affichée */
	private String distanceText;
	/** Vitesse affichée */
	private String speedText;

	private final PlanetService planetService;
	private final List<Planet> planets;
	Texture earthTexture;
	// Map des textures
	Map<String, Texture> textures = Maps.newHashMap();

	// Constructor
	public Project() {
		super();
		planetService = new PlanetServiceImpl();
		planets = planetService.loadPlanets();
		projectOptions = new ProjectOptions();
		setDimension(WINDOW_WIDTH, WINDOW_HEIGHT);
		start(WINDOW_TITLE);
	}

	// Methods
	@Override
	public void init(GLAutoDrawable gLDrawable) {
		// Load textures.
		loadTextures();
		super.init(gLDrawable);
	}

	/**
	 * Charge les textures de toutes les planètes
	 */
	private void loadTextures() {
		for (Planet planet : planets) {
			loadTexture(planet);
		}
	}

	/**
	 * Charge la texture d'une planète
	 * 
	 * @param planet
	 */
	private void loadTexture(Planet planet) {
		try {
			InputStream stream = getClass().getResourceAsStream(planet.getTexture());
			TextureData data = TextureIO.newTextureData(GLProfile.get(GLProfile.GL2), stream, false, "jpg");
			textures.put(planet.getName(), TextureIO.newTexture(data));
		} catch (IOException exc) {
			LOGGER.error("Fichier en erreur : " + planet.getTexture(), exc);
		}
	}

	@Override
	protected void animate() {

	}

	/**
	 * M�thode priv�e destin�e � faire tous les dessins dans le canvas OpenGL.<br>
	 * Elle est appel�e dans le display() de la classe m�re.
	 * 
	 * @param gl
	 */
	@Override
	protected void draw() {

		try {
			renderer.beginRendering(WINDOW_WIDTH, WINDOW_HEIGHT);
			// optionally set the color
			renderer.setColor(0.0f, 1.0f, 0.0f, 1.0f);
			renderer.draw("FPS		: " + fpsText + " f/s", 10, 10);
			renderer.draw("Distance	: " + distanceText + " km", 10, 25);
			renderer.draw("Vitesse	: " + speedText, 10, 40);
			renderer.setColor(1.0f, 1.0f, 1.0f, 1.0f);
			// ... more draw commands, color changes, etc.
			renderer.endRendering();
		} catch (Exception e) {
			LOGGER.debug("Exception lors du rendu du FPS", e);
		}

		draw(projectOptions, parameters);

		// FPS
		updateFPS();
		// Distance
		formatDistanceAndSpeed();

	}

	private void formatDistanceAndSpeed() {
		DecimalFormat df = new DecimalFormat();
		df.setDecimalSeparatorAlwaysShown(false);
		df.setMaximumFractionDigits(0);
		distanceText = df.format(translateZ * CURRENT_DISTANCE_RAPPORT);
		speedText = formatSpeed(df);
	}

	private String formatSpeed(DecimalFormat df) {
		StringBuilder speedText = new StringBuilder(df.format(speed)).append(" km/s ");
		if (speed == LIGHT_SPEED) {
			speedText.append("(Light-speed)");
		}
		if (speed > LIGHT_SPEED) {
			speedText.append("(Warp-speed)");
		}
		return speedText.toString();
	}

	/**
	 * Met à jour le FPS du site. <br/>
	 * TODO A mieux documenter et à améliorer
	 */
	private void updateFPS() {
		long time = System.currentTimeMillis();
		long diff = time - lastTimeFPSRendering;
		if (diff >= 1000) {
			fpsText = fps;
			lastTimeFPSRendering = time;
			fps = 0;
		} else {
			fps++;
		}
	}

	@Override
	protected void createLights() {

		float[] lightPosition = { 1.0f, 1.0f, 1.0f, 0.0f };
		float[] whiteColor = { 1.0f, 1.0f, 1.0f, 1.0f };
		FloatBuffer whiteLight = FloatBuffer.wrap(whiteColor);

		gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_POSITION, lightPosition, 0);
		gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_DIFFUSE, whiteLight);
		gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_SPECULAR, whiteLight);

		enableLights();
	}

	@Override
	public void resetCamera() {
		rotateX = 0;
		rotateY = 0;
	}

	@Override
	public void dispose(GLAutoDrawable arg0) {}

	@Override
	public void reshape(GLAutoDrawable gLDrawable, int arg1, int arg2, int width, int height) {
		GL2 gl = gLDrawable.getGL().getGL2();
		if (height <= 0) {
			height = 1;
		}
		float h = (float) width / height;
		gl.glMatrixMode(GLMatrixFunc.GL_PROJECTION);
		gl.glLoadIdentity();
		glu.gluPerspective(50.0f, h, 1.0, 1000.0);
		gl.glMatrixMode(GLMatrixFunc.GL_MODELVIEW);
		gl.glLoadIdentity();
	}

	@Override
	public void enableLights() {
		// FIXME Refactorer l'accès aux options
		if ((Boolean) options.getOption(NewOption.LIGHTS.getLabel()).getValue()) {
			gl.glEnable(GL2.GL_LIGHTING);
			gl.glEnable(GL2.GL_LIGHT0);
			gl.glEnable(GL2.GL_DEPTH_TEST);
		} else {
			gl.glDisable(GL2.GL_LIGHTING);
			gl.glDisable(GL2.GL_LIGHT0);
			gl.glDisable(GL2.GL_DEPTH_TEST);
		}
	}

	@Override
	public MenuBar createMenuBar() {
		return new ProjectMenuBar<Project>(this);
	}

	/**
	 * Affiche les planètes à l'écran
	 * 
	 * @param parameters
	 */
	private void drawPlanets(Parameters parameters) {
		GL2 gl = parameters.getGl();
		GLU glu = new GLU();

//		Util3D.drawPlanet(gl, glu, 5.0f, 0, textures.get("Mercure"));
		for (Planet planet : planets) {
			Texture texture = textures.get(planet.getName());
			texture.enable(gl);
			texture.bind(gl);
			Util3D.drawPlanet(gl, glu, planet);
		}

	}

	/**
	 * Affiche une des options choisie à l'écran
	 * 
	 * @param options
	 * @param parameters
	 */
	public void draw(ProjectOptions options, Parameters parameters) {
		if (options.isShowSphere()) {
			Util3D.drawSphere_v2(parameters, 10.0, Util3D.CENTER_POINT);
		}
		if (options.isShowOrbitalPlanets()) {
			Util3D.drawOrbitalPlanets(parameters);
		}
		if (options.isShowKeplerOvoid()) {
			Util3D.drawKeplerOvoid_v2(parameters, 15.0);
		}
		if (options.isShowEllipsoid()) {
			Util3D.drawEllipsoide(parameters, 10.0, 5.0, Util3D.CENTER_POINT);
		}
		if (options.isShowSuperFormula()) {
			SuperFormulaParameters p = new SuperFormulaParameters(2.0, 2.0, 19.0, 9.0, 14.0, 11.0);
			Util3D.drawSuperFormula(parameters, p);
		}
		if (options.isShowPlanets()) {
			drawPlanets(parameters);
		}
	}

	// Main program
	public static void main(String[] args) {
		canvas.addGLEventListener(new Project());
	}

	public ProjectOptions getProjectOptions() {
		return projectOptions;
	}

}